package jpave.query_interface;

/********************************************************
 * The MainTopRowTab does the Overview, Align DB hits, Next and Prev buttons
 * This file does the rest.
 */
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumnModel;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Vector;
import java.io.File;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;

import jpave.dataholders.MainData;
import jpave.dataholders.ContigData;
import jpave.dataholders.SequenceData;
import jpave.dataholders.CodingRegion;
import jpave.manager.ManagerFrame;
import util.Debug.ErrorReport;
import util.methods.PaveProps;
import util.methods.UIHelpers;
import util.ui.MenuMapper;
import util.ui.MultilineTextPanel;
import util.ui.UserPrompt;

public class ContigOverviewPanel  extends JPanel implements MouseListener, ClipboardOwner {
	private static final long serialVersionUID = 4196828062981388452L;
	private static final int NUM_DISPLAY_ROWS = 15;
	private static final int MAX_TABLE_WIDTH = 550;
	final int libCol = 5; // number of columns of lib data
	
	public ContigOverviewPanel (JPaveFrame frame, MainData inTheCluster )
	{
		theCluster = inTheCluster;
		paveFrame = frame;
		
		Iterator<ContigData> iterContig = theCluster.getContigIterator();
		if ( iterContig.hasNext() )
			ctgData = (ContigData)iterContig.next ();
		else {
			ErrorReport.die("Internal error in Sequence Overview");
		}
	
		createPanel();
		createToolbar ();
		
		// Scroll panel
		// QueryTab creates a scroller, but this still appears necessary
		scroller = new JScrollPane ( overviewPanel );
		scroller.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
		scroller.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED );
		scroller.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		UIHelpers.setScrollIncrements( scroller );
        
		// Add to the main panel
		setLayout( new BoxLayout ( this, BoxLayout.PAGE_AXIS ) );
		add ( toolPanel );
		add ( Box.createVerticalStrut(5) );
		add ( scroller );	
	}	
	
	public int getFilterType() { return filterType;}
	
	private void createPanel() {
		String textArea = makeTextArea();
		int numHits = makeHitTable();
		if (numHits!=0) createTablePanel();
		
		overviewPanel = new JPanel ( );
		overviewPanel.setLayout( new BoxLayout ( overviewPanel, BoxLayout.PAGE_AXIS ) );
		overviewPanel.setBackground( Color.WHITE );
		overviewPanel.add( Box.createVerticalStrut(10) );
		super.setBackground(Color.WHITE);
		
		textAreaTop = new JTextArea ( textArea );
		textAreaTop.setEditable( false );		
		textAreaTop.setFont(new Font("monospaced", Font.PLAIN, 12));
		int nTextWidth = Math.max((int)textAreaTop.
				getPreferredSize().getWidth() + 5, 600);		
		textAreaTop.setMaximumSize( new Dimension ( nTextWidth, 
				(int)textAreaTop.getPreferredSize().getHeight() ) );	
		textAreaTop.addMouseListener(this);

		JPanel tableAndHeader = new JPanel ();
		tableAndHeader.setLayout( new BoxLayout ( tableAndHeader, BoxLayout.PAGE_AXIS ) );
		tableAndHeader.setBackground( Color.WHITE );
		textAreaTop.setAlignmentX(LEFT_ALIGNMENT);
		tableAndHeader.add ( textAreaTop );

		if(theHitTable != null)
			tableAndHeader.add ( resultScroll );
		
		if (numHits > 0) { // && !PaveProps.isMac()) {// CAS 12/26/14 URL doesn't work on Mac, but does in applet
			
			MultilineTextPanel textPanel = null;
			try {
				textPanel = new MultilineTextPanel ( JPaveFrame.getTextFont(), 
					ctgData.getUniProtURL(), 1, 200, 1 ); 
		        textPanel.setBackground( Color.WHITE );   
			} catch (Exception e) {
				ErrorReport.reportError(e, "Internal error: making MultilineTextPanel");
			}
		
			tableAndHeader.add( Box.createVerticalStrut(5) );
			tableAndHeader.add( textPanel );
			tableAndHeader.add( Box.createVerticalStrut(5) );			
		}
		tableAndHeader.setBorder( BorderFactory.createLineBorder( Color.BLACK ) );
			
		overviewPanel.add( tableAndHeader );
		overviewPanel.add( Box.createVerticalStrut(10) );			
		invalidate();
		
		overviewPanel.add ( Box.createVerticalGlue() );	
	}
	
	private JPanel createToolbar ( )
	{		
		menuLib = new JComboBox ();
		Dimension dim = new Dimension ( 210, (int)menuLib.getPreferredSize().getHeight() );
		menuLib.setPreferredSize( dim );
		menuLib.setMaximumSize ( dim );
		menuLib.addItem( new MenuMapper ( "Show >0 Counts", 1));
		menuLib.addItem( new MenuMapper ( "Show >0 RPKM", 2 )); 
		menuLib.addItem( new MenuMapper ( "Show all Counts", 3 )); 
		menuLib.addItem( new MenuMapper ( "Show all RPKM", 4 )); 
		menuLib.addItem( new MenuMapper ( "Show replicates", 5 )); 

		menuLib.setBackground(Color.WHITE);
		menuLib.addActionListener
		( 	new ActionListener () 
			{
				public void	actionPerformed(ActionEvent e)
				{
					MenuMapper selection = (MenuMapper)menuLib.getSelectedItem();
					int type = selection.asInt();
					if (type == 1) 		{allLibs = false; normLibs = false; repLibs=false;}
					else if (type == 2)  {allLibs = false; normLibs = true; repLibs=false;}
					else if (type == 3) {allLibs = true; normLibs = false; repLibs=false;}
					else if	(type == 4)	{allLibs = true; normLibs = true; repLibs=false;}
					else {allLibs=false; normLibs=false; repLibs=true;}
					redraw();
				}
			}
		);		
		
		menuHit = new JComboBox ();
		dim = new Dimension ( 350, (int)menuLib.getPreferredSize().getHeight() );
		menuHit.setPreferredSize( dim );
		menuHit.setMaximumSize ( dim );
		menuHit.addItem( new MenuMapper ( "List DB hits - best eval & anno", 16 ) );
		menuHit.addItem( new MenuMapper ( "List DB hits - best per annoDB", 2 ) );
		menuHit.addItem( new MenuMapper ( "List DB hits - minimal overlap", 1 ) );
		menuHit.addItem( new MenuMapper ( "List DB hits - unique species", 4 ) );
		menuHit.addItem( new MenuMapper ( "List DB hits - unique gene rep", 8 ) );
		menuHit.addItem( new MenuMapper ( "List DB hits - total", 0));
		menuHit.setBackground(Color.WHITE);
		menuHit.addActionListener
		( 	new ActionListener () 
			{
				public void	actionPerformed(ActionEvent e)
				{
					MenuMapper selection = (MenuMapper)menuHit.getSelectedItem();
					int type = selection.asInt();
					filterType = type;	
					redraw();
				}				
			}
		);	
		
		final JPopupMenu popup = new JPopupMenu();
		popup.add(new JMenuItem(new AbstractAction("Copy sequence to clipboard") {
			private static final long serialVersionUID = 4692812516440639008L;
			public void actionPerformed(ActionEvent e) {
				saveTextToClipboard(getUnitransString());
			}
		}));
		
		popup.add(new JMenuItem(new AbstractAction("Copy revComp to clipboard") {
			private static final long serialVersionUID = 4692812516440639008L;
			public void actionPerformed(ActionEvent e) {
				saveTextToClipboard(getRevCompString());
			}
		}));

		popup.add(new JMenuItem(new AbstractAction("Copy ORF protein to clipboard") {
			private static final long serialVersionUID = 4692812516440639008L;
			public void actionPerformed(ActionEvent e) {
				saveTextToClipboard(getORFtransString());
			}
		}));

		popup.add(new JMenuItem(new AbstractAction("Save sequence to file (.fasta)") {
			private static final long serialVersionUID = -4657464918724936018L;
			public void actionPerformed(ActionEvent e) {
				saveTextToFile(getUnitransString(), "Seq.fasta");
			}			
		}));
		
		if (paveFrame.hasAssembly()) {
			popup.add(new JMenuItem(new AbstractAction("Save aligned reads to file (.fasta)") {
				private static final long serialVersionUID = -8613746472039641395L;
				public void actionPerformed(ActionEvent e) {
					saveTextToFile(getSequencesString(), "Reads.fasta");
				}
			}));
		}
		popup.add(new JMenuItem(new AbstractAction("Save DB hits to file (.fasta)") {
			private static final long serialVersionUID = 424842778593736605L;
			public void actionPerformed(ActionEvent arg0) {
				saveTextToFile(getAllDBHitsString(), "DBhits.fasta");
			}
		}));
		btnExport = new JButton("Copy/Export");
		btnExport.setBackground(ManagerFrame.PROMPTCOLOR);

		btnExport.addMouseListener(new MouseAdapter() {
	            public void mousePressed(MouseEvent e) {
	                popup.show(e.getComponent(), e.getX(), e.getY());
	            }
	        });

		JButton btnHelp = new JButton("Help");
		btnHelp.setBackground(ManagerFrame.HELPCOLOR);
		btnHelp.setMargin(new Insets(2, 2, 2, 2));
		btnHelp.setFont(new Font(btnHelp.getFont().getName(),Font.PLAIN,10));
		btnHelp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				UserPrompt.displayHTMLResourceHelp(paveFrame,"Sequence Details", 
						"html/viewSingleTCW/ContigOverviewPanel.html");
			}
		});
			
		// Top panel with buttons and drop-down		
		toolPanel = new JPanel ( );
		toolPanel.setLayout( new BoxLayout ( toolPanel, BoxLayout.LINE_AXIS ) );
		toolPanel.add( Box.createHorizontalStrut(5) );
		toolPanel.add( menuLib );
		toolPanel.add( Box.createHorizontalStrut(5) );
		toolPanel.add( menuHit );
		toolPanel.add( Box.createHorizontalStrut(5) );
		toolPanel.add( btnExport );
		toolPanel.add( Box.createHorizontalGlue() );
		toolPanel.add( btnHelp );
		toolPanel.add( Box.createHorizontalGlue() );
		toolPanel.setMaximumSize( new Dimension ( Integer.MAX_VALUE, 
				(int)toolPanel.getPreferredSize ().getHeight() ) );	
		toolPanel.setBackground(Color.WHITE);
		return toolPanel;
	}
	/***********************************************
	 * XXX Make the textArea
	 */
	private String makeTextArea() {
		boolean isProteinDB = paveFrame.isProteinDB();
		boolean isOneSeq = paveFrame.hasNoAssembly();
		int nUnitranSets = paveFrame.nContigSets();
		boolean hasGO = paveFrame.hasGOs();
		
		String textArea;
		if (!isProteinDB) textArea = "Sequence: " + ctgData + "\n\n";
		else textArea = "Protein: " + ctgData + "\n\n";
		
		try {
			String ctgName = ctgData.getContigID();
			int ctgID = ctgData.getCTGID();
			
			getDBdata(ctgID, paveFrame.selectedSession.getConnection(), 
					paveFrame.selectedSession.getAssemblyIDList(false));	
			int libNum = libNames.length;
			int libGTzero = nonZero(libCounts);
				
			int setNum = 0, ctgUniSet=0;
			boolean hasCtgSets = false;
			if (transNames.length>0) {
				hasCtgSets = true; 
				setNum = transNames.length;
				ctgUniSet = nonZero(transCounts);
			}
			ArrayList <SequenceData> hitData = ctgData.seqDataHitList();
			int hitNum=0;
			if (hitData!=null) hitNum=hitData.size();
		
			int maxCol=7,  maxRow=6;
			if (setNum > maxRow) maxRow = setNum;
			if (libNum > maxRow) maxRow = libNum;
			if (hitNum > maxRow) maxRow = hitNum;
			if (libCol > maxRow) maxRow = libCol;
			rows = new String [maxRow][maxCol];
		
			/////////// table 1 ////////////////////
			// column 1
			int c=0, r=1;
			rows[0][c] =     "Length:  " + ctgData.getConsensusBases();
			
			if (!isOneSeq) {
				rows[1][c] =     "Aligned: " + ctgData.getAllReads();
				rows[2][c] =     "Buried:  " + ctgData.getNumBuried();
				if (ctgData.hasNs()) 
					rows[3][c] = "Split  ";
				else rows[3][c] = "      ";
				r = 4;
			}
			else {rows[1][c]=rows[2][c]=rows[3][c]="  ";}
			c++;
			// column 2
			if (!isOneSeq && ctgData.hasSanger()) {
				rows[0][c] = "Mate Pairs: " + ctgData.getESTMatePairs();
				rows[1][c] = "5' ESTs:    " + ctgData.getEST5Prime();
				rows[2][c] = "3' ESTs:    " + ctgData.getEST3Prime();
				c++;
				r = 3;
			}
			// column 2 or 3
			int rr=0;
			if (!isProteinDB) {				
				if (!isOneSeq) rows[rr++][c] = "SNPs:        " + ctgData.getSNPCount();
				rows[rr++][c] = "GC content:  " + ctgData.getGCPercent() + "%";
				rows[rr++][c] = "Best ORF:    " + ctgData.getLongestORFCoords();
				// CAS 4/2/13 rows[rr++][c] = "Protein ORF: " + ctgData.getProteinORFCoords();
				c++; 
				if (r < rr) r = rr;
			}
			// column 2 or 3 or 4
			rr = 0;
			rows[rr++][c] =     "Libraries:    " + libGTzero;
			if (nUnitranSets > 1) 
				rows[rr++][c] = "Sequence Sets: " + ctgUniSet;
			if (paveFrame.hasPairWise())
				rows[rr++][c] = "PairWise:     " + ctgData.getCntPairwise();
		
			if (r < rr) r = rr;
			textArea += table(c+1, r);
		
			//// Table 2 - library counts either non-zero only or all; five per row

			if (normLibs) textArea += " Library RPKM:\n";
			else textArea += " Library Counts:\n";
			c=r=0;
			for(int x=0 ; x<libNames.length; x++) {
				if (libNames[x] == null) continue;
				if (!allLibs && libCounts[x] ==0 && libRPKM[x]==0) continue;
			
				if (!normLibs) {
					if (repLibs && libRepCntStr[x]!=null) 
						rows[r][c] = libNames[x] + ": " + libCounts[x] + " - " + libRepCntStr[x]; 
					else rows[r][c] = libNames[x] + ": " + libCounts[x]; 
				}
				else {
				    DecimalFormat df2 = new DecimalFormat( "#.#" );
					rows[r][c] = libNames[x] + ": " + df2.format(libRPKM[x]); 
				}
				c++;
				if (repLibs) {r++; c=0;}
				else if (c % libCol == 0) { r++; c=0;}
			}
			if (r!=0) c=libCol;
			textArea += table(c, r+1);
				
			//// Table 3 - count set counts as per library counts
			if (hasCtgSets && ctgUniSet > 0 && nUnitranSets>0 && paveFrame.hasAssembly()) {
				textArea += " Sequence Sets:\n";
				c=r=0;
				for(int x=0; x<transNames.length; x++) {
					if (transNames[x] == null) continue;
					if (!allLibs && transCounts[x] ==0) continue;
				
					rows[r][c] = transNames[x] + ": " + transCounts[x] + " "; 
					c++;
					if (c % libCol == 0) { r++; c=0;}
				}
				if (r!=0) c=libCol;
				textArea += table(c, r+1);
			}		

			//////////// table 4 ///////////////////
			c=0;
			rows[0][c] = "SwissProt: " + ctgData.getCntSwiss();
			rows[1][c] = "TrEMBL:    " + ctgData.getCntTrembl();
			rows[2][c] = "Other:     " + ctgData.getCntNT();
		
			c++;
			rows[0][c] = "Overlap:  " + ctgData.getCntOverlap();
			rows[1][c] = "Species:  " + ctgData.getCntSpecies();
			rows[2][c] = "Gene Rep: " + ctgData.getCntGene();
		
			c++;
			rows[0][c] = "Total Hits : " + ctgData.getCntTotal();
			rows[1][c] = "DB Types   : " + ctgData.getDBlist();
			if (hasGO) rows[2][c] = 
			             "GOs (Tree) : " + ctgData.getCntGO() 
			             				+" (" + ctgData.getCntGOTree() + ")";
		
			textArea += table(c+1, 3);	

			/* CAS 12/21/14 -- think this is just confusing. Can see on Go tree window
			if (hasGO) {
				String x = ctgData.getGOstr();
				if (!x.equals("")) 
					textArea += "Direct GOs (" + ctgData.getCntAssignGO() + 
					" direct Uniprot assignments):\n" + ctgData.getGOstr() + "\n\n";
			}
			*/
			//// Header label for main table of DB hits
		
			if (hitData != null) {
				String label="";
				r = 0;
				if ((filterType & 1) != 0) {label = "minimal overlap"; r = ctgData.getCntOverlap();}
				else if ((filterType & 2) != 0) {label = "best per annoDB"; r = ctgData.getCntDBs(); }
				else if ((filterType & 4) != 0) {label = "unique species"; r = ctgData.getCntSpecies();}
				else if ((filterType & 8) != 0) {label = "unique gene rep"; r = ctgData.getCntGene();}
				else if ((filterType & 16) != 0) {label = "best eval & anno"; r = 0;}
				else {label = "total"; r = ctgData.getCntTotal();}
			
				if (r>0) textArea += "DB hits - " + label + ": " + r + "\n";
				else textArea += "DB hits - " + label + "\n";
			}	
			else textArea += "No DB hits: \n";
		
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
		return textArea;
	}

	 /*  Make table in textArea */
	private String table(int nCol, int nRow)
	{
		String lines = "";
		int c, r;
		
		int []collen = new int [nCol];
		for (c=0; c<nCol; c++) collen[c]=0;
				
        // determine column width
		// not all columns may be filled for last row
        for (c=0; c< nCol; c++) {
            for (r=0; r<nRow && rows[r][c] != null; r++) 
            		if (rows[r][c].length() > collen[c]) 
            			collen[c] = rows[r][c].length();
 
        }
        for (c=0; c< nCol; c++)  collen[c] += 4;
        
        // Output rows
        for (r=0; r<nRow; r++) {
        		String row = " ";
            for (c=0; c<nCol && rows[r][c] != null; c++) {
                 row += pad(rows[r][c],collen[c],1);
                 rows[r][c] = null; // so wouldn't reuse in next table
            }
            lines += row + "\n";
        }
        lines += "\n";
		return lines;
	}
    private static String pad(String s, int width, int o)
    {
            if (s.length() > width) {
                String t = s.substring(0, width-1);
                System.err.println("'" + s + "' truncated to '" + t + "'");
                s = t;
                s += " ";
            }
            else if (o == 0) { // left
                String t="";
                width -= s.length();
                while (width-- > 0) t += " ";
                s = t + s;
            }
            else {
                width -= s.length();
                while (width-- > 0) s += " ";
            }
            return s;
    }
    
    private int nonZero(int [] list) {
	    	int cnt=0;
	    	if(list!=null)
	    		for(int x=0; x<list.length; x++) {
	    			if(list[x] > 0)
	    				cnt++; 
	    		}
	    	return cnt;
    }

	/********************************************************
	 * XXX Hit table code
	 * filter: 1=olap, 2=top3, 4=species, 8=gene, 16=besteval, 32=bestanno
	 */
	private int makeHitTable() {		
		ArrayList <SequenceData> hitData = ctgData.seqDataHitList();	
		if (hitData == null) return 0;
		
		int hitSize = hitData.size();
		Iterator<SequenceData> hit = hitData.iterator();
		
		int x = 0;	
		theHitData = new ArrayList<HitListData> ();
	
		while(hit.hasNext()) {
			SequenceData seq = hit.next();
			int fbit = seq.getDBfiltered();
			
			if (filterType != 0 && filterType != 2 && filterType!=16 && 
					(filterType & fbit) == 0) continue;
			if (filterType==2 && seq.getBlastHitData().getBlastRank() != 1) continue;
			if (filterType==16 && (fbit & 16) == 0 && (fbit & 32) == 0) continue;
			
			theHitData.add(new 	HitListData(x+1, seq.getName(), 
					seq.getBlastHitData().getEVal(), seq.getBlastHitData().getDBtypeTaxo(),
					seq.getDBdesc().trim(), seq.getDBspecies().trim(),
					(int) seq.getBlastHitData().getPercentID(),seq.getBlastHitData().getAlignLen(),
					seq.getBlastHitData().getCtgStart(), seq.getBlastHitData().getCtgEnd()));

			x++;
		}	
		return hitSize;
	}

	/**
	 * XXX HitListData class
	 * order of headings must correspond to order of SORT_BY values
	 */
	private String [] columnHeadings() {
		String [] x = { "Row", "Name", "Eval", "DB type", "Description", "Species",  "Percent", "AlignLen", "Start", "End"};
		return x;
	}
	private class HitListData {
		public static final int SORT_BY_RANK = 0; 
		public static final int SORT_BY_NAME = 1; 
		public static final int SORT_BY_EVAL = 2; 
		public static final int SORT_BY_TYPE = 3; 
		public static final int SORT_BY_DESCRIP = 4; 
		public static final int SORT_BY_SPECIES = 5;
		public static final int SORT_BY_PERCENT = 6; 
		public static final int SORT_BY_ALIGNLEN = 7; 
		public static final int SORT_BY_START = 8; 
		public static final int SORT_BY_END = 9; 
	
		public HitListData(int rank, String name, double eVal, String type, String description, String species, 
				int p, int len, int start, int end) {
			nRank = rank;
			hitID = name;
			dEVal = eVal;
			strType = type;
			strDescription = description;
			strSpecies = species;
			nPercent = p;
			nAlignLen = len;
			nStart = start;
			nEnd = end;
		}
		
		public Object getValueAt(int pos) {
			switch(pos) {
			case 0: return nRank;
			case 1: return hitID;
			case 2:
				if(dEVal == 0)
					return new String("0.0");
				return (new DecimalFormat("0.0E0")).format((Double)dEVal); 
			case 3: return strType;
			case 4: return strDescription;
			case 5: return strSpecies;
			case 6: return nPercent;
			case 7: return nAlignLen;
			case 8: return nStart;
			case 9: return nEnd;
			}
			return null;
		}
		
		public int compareTo(HitListData obj, boolean sortAsc, int field) {
			int order = 1;
			if(!sortAsc) order = -1;
			
			switch(field) {
			case SORT_BY_RANK: return order * (new Integer(nRank)).compareTo(new Integer(obj.nRank));
			case SORT_BY_NAME: return order * hitID.compareTo(obj.hitID);
			case SORT_BY_EVAL: return order * (new Double(dEVal)).compareTo(new Double(obj.dEVal));
			case SORT_BY_START: return order * (new Integer(nStart)).compareTo(new Integer(obj.nStart));
			case SORT_BY_END: return order * (new Integer(nEnd)).compareTo(new Integer(obj.nEnd));
			case SORT_BY_PERCENT: return order * (new Integer(nPercent)).compareTo(new Integer(obj.nPercent));
			case SORT_BY_ALIGNLEN: return order * (new Integer(nAlignLen)).compareTo(new Integer(obj.nAlignLen));
			case SORT_BY_TYPE: return order * strType.compareTo(obj.strType);
			case SORT_BY_DESCRIP: return order * strDescription.compareTo(obj.strDescription);
			case SORT_BY_SPECIES: return order * strSpecies.compareTo(obj.strSpecies);
			}
			return 0;
		}
		private int nRank = -1;
		private String hitID = "";
		private double dEVal = 0;
		private int nStart = -1;
		private int nEnd = -1;
		private int nPercent = -1;
		private int nAlignLen = -1;
		private String strType = "";
		private String strDescription = "";
		private String strSpecies = "";
	} // end HitData class
	
	/*
	 * XXX Table support routines
	 */
	private void createTablePanel() {
		theHitTable = new JTable();
		theHitTable.setColumnSelectionAllowed( false );
		theHitTable.setCellSelectionEnabled( false );
		theHitTable.setRowSelectionAllowed( true );
		theHitTable.setShowHorizontalLines( false );
		theHitTable.setShowVerticalLines( false );	
		theHitTable.setIntercellSpacing ( new Dimension ( 1, 0 ) );
		theModel = new QueryResultsTableModel(theHitData);
		theHitTable.setModel(theModel);
		
		JTableHeader header = theHitTable.getTableHeader();
		header.setUpdateTableInRealTime(true);
		header.addMouseListener(theModel.new ColumnListener(theHitTable));
		header.setReorderingAllowed(true);
		resizeColumns();
		
		resultScroll = new JScrollPane(theHitTable);
		resultScroll.setBorder( null );
		Dimension size = theHitTable.getPreferredSize();
		size.height = NUM_DISPLAY_ROWS * theHitTable.getRowHeight(); 
		size.width = Math.min(MAX_TABLE_WIDTH, theHitTable.getWidth());
		resultScroll.setPreferredSize(size);
		resultScroll.getViewport().setBackground(Color.WHITE);
		resultScroll.setAlignmentX(LEFT_ALIGNMENT);			
	}
	
	private void resizeColumns() {
		theHitTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		int margin = 2;
		
		for(int x=0; x < theHitTable.getColumnCount(); x++) {
			DefaultTableColumnModel colModel = (DefaultTableColumnModel) 
				theHitTable.getColumnModel();
			TableColumn col = colModel.getColumn(x);
			int maxSize = 0;
			
			TableCellRenderer renderer = col.getHeaderRenderer();
			
			if(renderer == null)
				renderer = theHitTable.getTableHeader().getDefaultRenderer();
			
			Component comp = renderer.getTableCellRendererComponent(theHitTable, 
					col.getHeaderValue(), false, false, 0, 0);
			maxSize = comp.getPreferredSize().width;
			
			for(int y=0; y<theHitTable.getRowCount(); y++) {
				renderer = theHitTable.getCellRenderer(y, x);
				comp = renderer.getTableCellRendererComponent(theHitTable, 
						theHitTable.getValueAt(y, x), false, false, y, x);
				maxSize = Math.max(maxSize, comp.getPreferredSize().width);
			}
			maxSize += 2 * margin;
			
			col.setPreferredWidth(maxSize);
		}
		((DefaultTableCellRenderer) 
				theHitTable.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.LEFT);
	}
	
	public void mousePressed(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseClicked(MouseEvent e) {}

	private class QueryResultsTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 774460555629612058L;
		
		private final String [] theLabels = columnHeadings();
		
		public QueryResultsTableModel(ArrayList<HitListData> displayValues) { 
			theDisplayValues = displayValues; }
		public int getColumnCount() { return theLabels.length; }
        public int getRowCount() { return theDisplayValues.size(); }
        public Object getValueAt(int row, int col) { 
        	return theDisplayValues.get(row).getValueAt(col); }
        public String getColumnName(int col) { return theLabels[col]; }
        
        private ArrayList<HitListData> theDisplayValues = null;
		private boolean sortAsc = true;
		private int sortMode = HitListData.SORT_BY_RANK;
        
  	  	public class ColumnListener extends MouseAdapter {
		    protected JTable table;

		    public ColumnListener(JTable t) {
		    		table = t;
		    }

		    public void mouseClicked(MouseEvent e) {
			    	TableColumnModel colModel = table.getColumnModel();
			    	int columnModelIndex = colModel.getColumnIndexAtX(e.getX());
			    	int modelIndex = colModel.getColumn(columnModelIndex).getModelIndex();
	
			    	if (modelIndex < 0)
			    		return;
			    	if (sortMode == modelIndex)
			    		sortAsc = !sortAsc;
			    	else
			    		sortMode = modelIndex;
	
			    	for (int i = 0; i < table.getColumnCount(); i++) { 
			    		TableColumn column = colModel.getColumn(i);
			    		column.setHeaderValue(getColumnName(column.getModelIndex()));
			    	}
			    	table.getTableHeader().repaint();
	
			    	Collections.sort(theDisplayValues, new HitListComparator(sortAsc, sortMode));
			    	table.tableChanged(new TableModelEvent(QueryResultsTableModel.this));
			    	table.repaint();
		    }
  	  	}
	}

	private class HitListComparator implements Comparator<HitListData> {
		public HitListComparator(boolean sortAsc, int mode) {
			bSortAsc = sortAsc;
			nMode = mode;
		}		
		public int compare(HitListData obj1, HitListData obj2) { 
			return obj1.compareTo(obj2, bSortAsc, nMode); }
		
		private boolean bSortAsc;
		private int nMode;
	}
	
	public int cntSelectedHits() {
		if(theHitTable == null) return 0;
		int [] selections = theHitTable.getSelectedRows();
		return selections.length;
	}
	public String [] getSelectedHits() {
		int [] selections = theHitTable.getSelectedRows();

		Vector<String> retVal = new Vector<String> ();
		for(int x=0; x<selections.length; x++) {
			String hitID = theHitData.get(selections[x]).hitID;
	
			if(!retVal.contains(hitID)) retVal.add(hitID);
		}	
		return retVal.toArray(new String[0]);
	}
	/************  End Hit table code *************************/
	private void redraw() {
		scroller.remove(overviewPanel);
		createPanel();
		scroller.setViewportView( overviewPanel );
		repaint();
	}
	
	private void saveTextToClipboard(String lines) {
		Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
	    clipboard.setContents( new StringSelection(lines), (ClipboardOwner) this );

	}
	
	/*****************************************************
	 * XXX Export functions
	 */
	private String getUnitransString() {
		String retVal = ">" + ctgData.getContigID() + "  len=" + 
			ctgData.getSeqData().getLength() + "\n";
		retVal += ctgData.getSeqData().getSequence() + "\n";

		return retVal;
	}
	private String getRevCompString() {
		String retVal = ">" + ctgData.getContigID() + "  len=" + 
			ctgData.getSeqData().getLength() + " (rc)\n";
		retVal += ctgData.getSeqData().getSeqRevComp() + "\n";

		return retVal;
	}
	private String getORFtransString() {
		CodingRegion orf = ctgData.getORFCoding();
		String retVal = ">" + ctgData.getContigID() + " AAlen=" + 
				orf.getLength() + " rf=" + orf.getFrame() + "\n"; // CAS 12/21/14 corrected header
		// CAS 12/21/14 ORF was found with nogaps, yet translated with gaps; fixed in SequenceData
		retVal += ctgData.getSeqData().getORFtrans(orf) + "\n";

		return retVal;
	}
	
	private String getSequencesString() {
		Vector<SequenceData> sequences = ctgData.getAllSequences();
		Iterator<SequenceData> seqIter = sequences.iterator();
		
		String retVal = "";
		while(seqIter.hasNext()) {
			SequenceData seq = seqIter.next();
			retVal += ">" + seq.getName() + "  len=" + seq.getLength() + "\n";
			retVal += seq.getSequence() + "\n";
		}
		return retVal;
	}
	
	private String getAllDBHitsString() {
		String retVal = "";
		ArrayList <SequenceData> hitData = ctgData.seqDataHitList();
		Iterator<SequenceData> seqIter = hitData.iterator();
		
		while(seqIter.hasNext()) {
			SequenceData seq = seqIter.next();
			retVal += ">" + seq.getName() + "  len=" + seq.getLength() + "\n";
			retVal += seq.getSequence() + "\n";
		}
		
		return retVal;
	}
	
	private void saveTextToFile(String lines, String dName) {
		final JFileChooser fc = new JFileChooser(paveFrame.lastSaveFilePath);
		fc.setSelectedFile(new File(dName));
		final String theLines = lines;
		if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
			final File f = fc.getSelectedFile();
			int writeOption = JOptionPane.YES_OPTION;
			if (f.exists()) {
				writeOption = JOptionPane.showConfirmDialog(
					    this,
					    "The file already exists, overwrite it?",
					    "Save to File",
					    JOptionPane.YES_NO_OPTION);
			}
			if (writeOption == JOptionPane.YES_OPTION) {
				paveFrame.setLastPath(f.getPath());
				Thread theThread = new Thread(new Runnable() {
					public void run() {
					    	PrintWriter pw = null;
					    	try {
					    		System.out.println("Write to " + f.getPath()); // CAS 12/21/14
					    		pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
					    		pw.print(theLines);
					    		pw.close();
					    	}
					    	catch(Exception e) {ErrorReport.prtReport(e,  "Could not write to " + f.getPath());}
					}
				});
				theThread.start();
			}
		}
	}
	// called from MainTopRow for message before aligning
	public int getTotal() { return ctgData.getCntTotal();}
	public int getDBs() 	  { return ctgData.getCntDBs() * 3;}
	public int getSpecies() { return ctgData.getCntSpecies();}
	public int getGenes() { return ctgData.getCntGene();}
	public int getOlap() { return ctgData.getCntOverlap();}
	
	public void lostOwnership(Clipboard arg0, Transferable arg1) {
	}
	/**********************************************************
	 * XXX database calls
	 */
	private void getDBdata(int ctgID, Connection dbc, String inAssemblyID)
	{
	    Statement stmt = null;	
	    ResultSet rs = null;
	    
		try {			
			stmt = dbc.createStatement();
			rs = stmt.executeQuery("SELECT count(*) from library");
			rs.next();
 			int nLib = rs.getInt(1)+1;
 			
 			// there will be null entries, but the mysql index will correspond to the name entry
 			// this allows reps to be shown with their names 
 			libNames = new String [nLib];
 			transNames = new String [nLib]; 	        
 			libRepCntStr = new String[nLib];
 			for (int i=0; i<nLib; i++) libNames[i]=transNames[i]=libRepCntStr[i]=null;
  			String fields=null;
  			
            rs = stmt.executeQuery("SELECT LID, libid, ctglib FROM library");
    			while(rs.next ())
    			{
    				int id = rs.getInt(1);
    				String name = rs.getString(2);
    				int ctglib = rs.getInt(3);
    				
    				if (ctglib==0) libNames[id] = name;
    				else transNames[id] = name;
    				
    				if (fields==null) fields = "L__" + name;
    				else fields += ",L__" + name;
    				if (ctglib==0) fields += ",LN__" + name;
    			}

    			libCounts = new int[nLib];
    			libRPKM = new double[nLib];   	
    			transCounts = new int[nLib];
    			rs = stmt.executeQuery("SELECT " + fields + " FROM contig WHERE CTGID=" + ctgID);
    			while(rs.next()) {
    				for(int x=0; x<nLib; x++) {
    					if (libNames[x] != null) {
    						libCounts[x] = rs.getInt("L__" + libNames[x]);
    						libRPKM[x] = rs.getInt("LN__" + libNames[x]); 
    					}
    				}
    				for(int x=0; x<nLib; x++) {
    					if (transNames[x] !=null)
    						transCounts[x] = rs.getInt("L__" + transNames[x]);
    				}
    			}
    			// get replicas
            rs = stmt.executeQuery("SELECT LID, count, rep FROM clone_exp where CID=" + ctgID);
            while (rs.next()) {
            		int rep = rs.getInt(3);
            		if (rep !=0) {
            			int id = rs.getInt(1);
            			int cnt = rs.getInt(2);	
            			if (libRepCntStr[id]==null) libRepCntStr[id] = " " + cnt;
            			else libRepCntStr[id] += ", " + cnt;
            		}
            }       
    			rs.close();    		
    			stmt.close();
		}
		catch ( Exception err ) {
			ErrorReport.reportError(err, "Error: reading database for getLibs");
        }
	}
	
	// from database that is not in ctgData
	private String [] 	libNames;
	private String [] 	transNames;
	private int [] 		libCounts;
	private double [] 	libRPKM;
	private int [] 		transCounts;
	private String []	libRepCntStr;
	
	private JScrollPane scroller = null;
	private JPanel overviewPanel = null;
	private JPanel toolPanel = null;
	private JComboBox menuLib = null;
	private JComboBox menuHit = null;
	private JTextArea textAreaTop = null;
	private JTable theHitTable = null;
	private JScrollPane resultScroll = null;
	private JButton btnExport = null;
	
	private JPaveFrame paveFrame; 
	private MainData theCluster;
	private ContigData ctgData;
	private String [][] rows = null;
	private QueryResultsTableModel theModel = null;
	
	private boolean allLibs = false;
	private boolean normLibs = false;
	private boolean repLibs = false;
	private int filterType = 16;
	private ArrayList<HitListData> theHitData = null;
}
